local tree = require('tree')

local function check_config(config)
    assert(config and config.elements and config.expressions and config.class, "invalid param.")
end

local function array_to_map(elements)
    local map = {}
    for i, element in ipairs(elements) do
        print(i, element.type, element.name)
        map[element.name] = element;
    end
    return map
end

local function tokenize(expr)
    local tokens = {}
    for tok in string.gmatch(expr, '[^%s<>]+') do
        table.insert(tokens, tok)
    end
    return tokens
end

local function map_tokens_to_elements(tokens, elements)
    local map = {}
    for _, tok in ipairs(tokens) do
        local message = string.format("element not defined for token '%s'", tok)
        local element = assert(elements[tok], message)
        table.insert(map, element)
    end
    return map
end

local function parse_expressions(config, cmd_tree)
    local elements = array_to_map(config.elements)
    for _, expr in ipairs(config.expressions) do
        local tokens = tokenize(expr)
        local tok_elements = map_tokens_to_elements(tokens, elements)
        local parent
        for _, elem in ipairs(tok_elements) do
            parent = cmd_tree:add_node(tree.new_node(elem), parent)
        end
    end
end

local cli = {}

function new_cli_instance()
    return setmetatable({cmd_tree = tree.new()}, {__index = cli})
end

function cli:command(config)
    check_config(config)
    parse_expressions(config, self.cmd_tree)
end

function cli:excute(cmd)
    local tokens = tokenize(cmd)
    if tokens and #tokens > 0 then
        local success = true
        local root = self.cmd_tree.root
        for _, tok in ipairs(tokens) do
            local node = root:get_child(tok)
            if node == nil then
                success = false
                io.write(string.format("Unknown command element '%s'\n", tok))
                break
            end
            root = node
        end
        if success then
            io.write(string.format("excute succeed.\n"))
        else
            io.write('Operation abnormal.\n')
        end
        io.flush()
    end
end

local function keyword(name, desc, dynamic_help, dynamic_check)
    return {
        type = 'keyword',
        name = name,
        description = desc,
        dynamic_help = not not dynamic_help,
        dynamic_check = not not dynamic_check
    }
end

local function parameter(name, desc, datatype, dynamic_help, dynamic_check)
    return {
        type = 'parameter',
        name = name,
        description = desc,
        datatype = datatype,
        dynamic_help = not not dynamic_help,
        dynamic_check = not not dynamic_check
    }
end

local cli_instance = new_cli_instance()

return {
    instance = function()
        return cli_instance
    end,
    keyword = keyword,
    parameter = parameter,
    datatype = require('datatype')
}
